/*****************************************************************************\
 *                            RESTRICTED RIGHTS LEGEND
 *
 *      Use, duplication, or disclosure by the Government is subject to
 *      restrictions as set forth in subparagraph (c) (1) (ii) of the Rights in
 *      Technical Data and Computer Software Clause at DFARS 252.227-7013.
 *
 *      The Board of Trustees of the University of Illinois
 *      109 Coble Hall
 *      801 South Wright Street
 *      Champaign, IL 61820-6242
 *
 *      File: AclManager.cs
 *
 *      Authors: Erich Heine, David Rodgers, Denis Kholine, Derek Dagit,
 *      William H. Sanders, Klara Nahrstedt, Rakesh Bobba, Tim Yardley
 *      Creation Date: 2012
 *
 *      Copyright (c) 2012 William H. Sanders, Klara Nahrstedt, Erich Heine,
 *      Rakesh Bobba, Tim Yardley,
 *      and The Board of Trustees of the University of Illinois
 *      All rights reserved.
 * 
 *      No further use, distribution, or disclosure beyond internal development 
 *      project participants (Grid Protection Alliance and University of Illinois)
 *      is allowed.
 *
 \*****************************************************************************/

﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using System.Runtime.Remoting.Messaging;

namespace SIEGate.Acl
{
    // This could be replaced by an AccessRule, but it is here in case we want
    // to add more interesting information to arguments passed to the event 
    // handlers.
    [Serializable]
    [ComVisible(false)]
    public class AclChangedEventArgs : EventArgs
    {
        public AccessRule rule
        {
            get;
            private set;
        }


        public AclChangedEventArgs(AccessRule rule)
        {
            this.rule = rule;
        }

        public bool Equals(AclChangedEventArgs other)
        {
            return this.rule == other.rule;
        }
    }


    public delegate void AclChangedHandler(IAclProvider provider, AclChangedEventArgs args);



    public static class AclManager
    {
        private static readonly IAclProvider provider = new MemoryProvider();

        private static Dictionary<Resource,HashSet<AclChangedHandler>> aclDeletedHandlers = new Dictionary<Resource, HashSet<AclChangedHandler>>();

        public static IAclProvider Allow(Subject subject, Verb verb, Resource resource)
        {
            return provider.AddAcls(new AllowRule(subject, verb, resource));
        }

        public static IAclProvider Deny(Subject subject, Verb verb, Resource resource)
        {
            return provider.DeleteAcls(new AllowRule(subject, verb, resource));
        }

        public static bool IsAllowed(Subject subject, Verb verb, Resource resource)
        {
            IEnumerable<AccessRule> aclList = provider.GetAclsBySubject(subject);

            return 0 < aclList.Where(rule => rule.verb == verb 
                //&& rule.type == AccessRuleType.Allow
                && rule.resource.Equal(resource)).Count();
        }

        public static void SubscribeToRuleDeletions(Resource resource, AclChangedHandler handler)
        {
            if (0 == aclDeletedHandlers.Count())
            {
                provider.AclWasDeleted += fireRelevantEvents;
            }

            HashSet<AclChangedHandler> handlersForResource;

            if (aclDeletedHandlers.TryGetValue(resource, out handlersForResource))
            {
                handlersForResource.Add(handler);
            }
            else
            {
                handlersForResource = new HashSet<AclChangedHandler>();
                handlersForResource.Add(handler);
                aclDeletedHandlers[resource] = handlersForResource;
            }
        }

        public static void UnSubscribeFromRuleDeletions(Resource resource, AclChangedHandler handler)
        {
            HashSet<AclChangedHandler> handlersForResource;
            if (aclDeletedHandlers.TryGetValue(resource, out handlersForResource))
            {
                handlersForResource.Remove(handler);
            }

            if (0 == handlersForResource.Count())
            {
                aclDeletedHandlers.Remove(resource);
            }

            if (0 == aclDeletedHandlers.Count())
            {
                provider.AclWasDeleted -= fireRelevantEvents;
            }
        }


        private static void fireRelevantEvents(IAclProvider sender, AclChangedEventArgs eventArgs)
        {
            // Load...
            HashSet<AclChangedHandler> relevantHandlers;
            if (!aclDeletedHandlers.TryGetValue(eventArgs.rule.resource, out relevantHandlers))
            {
                return; // No one is listening.
            }


            // Fire!!!
            foreach (AclChangedHandler h in relevantHandlers)
            {
                h.BeginInvoke(sender, eventArgs, new AsyncCallback(noopAsyncCallback), null);
            }
        }

        // This frees the firing of events from having to wait until all are handled.
        // But we do not use the results for anything.
        private static void noopAsyncCallback(IAsyncResult iar)
        {
            AsyncResult ar = (AsyncResult)iar;
            AclChangedHandler caller = (AclChangedHandler) ar.AsyncDelegate;
            caller.EndInvoke(ar);
        }
    }
}
